
import 'dart:async';
import 'dart:io';

import 'package:flutter/foundation.dart';
import 'package:get/get.dart';
import 'package:platform_device_id/platform_device_id.dart';
import 'package:share_fit/app/controllers/home/weather/logic.dart';
import 'package:share_fit/app/helper/ext/tr_ext.dart';
import 'package:share_fit/app/helper/health_helper.dart';
import 'package:share_fit/app/helper/storage_helper.dart';
import 'package:share_fit/app/helper/storage_mixin.dart';
import 'package:share_fit/app/helper/upgrade_helper.dart';
import 'package:share_fit/app/network/net_tool.dart';
import 'package:share_fit/app/routes/device_routes.dart';
import 'package:share_fit/app/tools/app_const.dart';
import 'package:share_fit/app/tools/app_util.dart';
import 'package:share_fit/app/tools/color_const.dart';
import 'package:share_fit/app/tools/log_utils.dart';
import 'package:share_fit/app/ute/model/device_info.dart';
import 'package:share_fit/app/ute/uteped_bind.dart';
import 'package:share_fit/app/ute/uteped_config.dart';
import 'package:share_fit/app/ute/uteped_const.dart';
import 'package:share_fit/app/ute/uteped_helper.dart';
import 'package:share_fit/app/ute/uteped_mixin.dart';
import 'package:share_fit/generated/locales.g.dart';
import 'package:uuid/uuid.dart';

abstract class DeviceChangeListener {
  void onDeviceChangeEvent(event);
}

class UTEPedDevice with UTEPedMixin, StorageMixin implements UTEPedListener {
  /// 主要用于设备连接管理
  /// 需要注意的地方，目前sdk跟手表通信事件比较长，扫描设备在20秒左右，连接设备也要到14秒左右，设备从连接中到连接上时间大概在5秒
  /// 目前sdk个手表只有一个disconnect断开连接的接口，设计的解绑和断开是不存在的，在切换设备的时候，要我们自己维护，目前sdk方提供的demo没有这方面可参照的，它是单一设备连接
  /// 切换设备目前的流程是先断开当前设备，等完成之后再连接新的设备，整个时间周期比较长
  /// 2024.01.25 更新： 新增了BT3绑定检测支持，函数checkBT3，完成绑定之后手表的来电通话提醒才能正常工作

  static const path = "device";

  String _userId = "";
  String _address = "";
  String _unbindAddress = "";
  String battery = "";

  int currentDeviceStatus = UTEPedConst.stateIDLE;
  Timer? _connectTimer;
  Timer? _unbindTimer;
  List deviceList = [];
  DeviceInfo deviceInfo = DeviceInfo();

  /// 记录单一操作状态，自行管理，不考虑手表段的主动变化所发出的通知
  int userState = UTEPedConst.userStateIDLE;
  int retryCount = 0;

  List<DeviceChangeListener> listeners = [];
  late UTEPedConfig _config;
  late UTEPedBind _onlineBind;
  List onlineDevices = [];

  static final UTEPedDevice _instance = UTEPedDevice._();
  static UTEPedDevice get instance => _instance;
  UTEPedDevice._();

  void init() {
    _config = UTEPedConfig.instance;
    _config.init();
    _onlineBind = UTEPedBind();
    _onlineBind.init();
    // 开启或关闭sdk日志
    _config.setLogEnable(AppConst.isDebug);
    // 开启或关闭sdk数据自动同步
    _config.setSyncDataEnable(true);
    // 开启本地日志文件保存，/sdcard/Download
    _config.setLogFileEnable(false);

    if (kDebugMode) {
      // 蓝牙设备不过滤，方便设备调试
      _config.setDeviceFilter(false);
    }

    UpgradeHelper.instance.init();

    initStorage();

    deviceList = read(UTEPedConst.keyDeviceList, defaultValue: [], global: true);
    if (deviceList.isNotEmpty) {
      _initDevice();
    }

    _address = read(UTEPedConst.keyAddress, global: true);
    var userId = read(UTEPedConst.keyUserId, global: true);
    if (kDebugMode) {
      userId = "1234567890";
      _userId = userId;
    }
    if (userId == null || userId.isEmpty) {
      getDeviceId().then((value) => _saveUserId(value));
    }
    else {
      _userId = userId;
      _autoConnectDevice();
    }
  }

  UTEPedConfig getUteConfig() {
    return _config;
  }

  void _initDevice() {
    if (deviceList.isNotEmpty) {
      String address = read(UTEPedConst.keyAddress, global: true);
      for (var device in deviceList) {
        if (device[UTEPedConst.keyAddress] == address) {
          deviceInfo.address = device[UTEPedConst.keyAddress];
          deviceInfo.model = device[UTEPedConst.keyModel];
          break;
        }
      }
    }
  }

  void addListener(DeviceChangeListener listener) {
    if (listeners.contains(listener)) {
      return;
    }
    listeners.add(listener);
  }

  void removeListener(DeviceChangeListener listener) {
    if (listeners.contains(listener)) {
      listeners.remove(listener);
    }
  }

  @override
  String getRemotePath() {
    return path;
  }

  bool getTakePhotoSwitchStatus() {
    return read('sendCameraPermission', defaultValue: false);
  }

  @override
  void onRemoteEvent(event, method) {
    if (method == UTEPedConst.methodConnect || method == UTEPedConst.methodDisconnect) {
      String address = event[UTEPedConst.keyAddress];
      int status = event[UTEPedConst.keyStatus];
      if (status == UTEPedConst.stateConnected) {
        logger.d("onRemoteEvent==================");
        _cancelConnectTimer();
        _unbindAddress = "";
        if (address != _address) {
          _address = address;
        }
        if (userState != UTEPedConst.userStateConnected) {
          refreshDevice();
          _config.syncData();
          _config.setDefaultConfig();
          checkOnlineDevices();
        }
      }
      else if (status == UTEPedConst.stateDisconnected) {
        int disconnectType = event[UTEPedConst.keyType];
        if (_unbindAddress == address || disconnectType == UTEPedConst.disconnectTypeBond) {
          /// 手动解绑
          _disconnectDevice(address);
          removeOnlineDevice(address);
        }
        if (userState == UTEPedConst.userStateDisconnecting) {
          userState = UTEPedConst.userStateDisconnected;
        }
        else if (userState == UTEPedConst.userStateConnecting && _address == address) {
          _connectTimer?.cancel();

          notifyDeviceChanged({UTEPedConst.keyAction: UTEPedConst.actionConnectReject, UTEPedConst.keyAddress:_address});
        }
      }
    }
    else if(method == UTEPedConst.methodGetBatteryInfo) {
      String address = event[UTEPedConst.keyAddress];
      if (_address == address) {
        battery = event[UTEPedConst.keyBattery].toString();
        save(UTEPedConst.keyBattery, battery, global: true);
      }
      _updateDeviceListBattery(address, battery);
    }
    else if(method == UTEPedConst.methodSetScreenAutoLight) {
      save(UTEPedConst.methodSetScreenAutoLight, event[UTEPedConst.keyEnable]);
    }
    else if(method == UTEPedConst.methodCameraControl) {
      if (event[UTEPedConst.keyValue] == UTEPedConst.openCamera && getTakePhotoSwitchStatus()) {
        Get.toNamed(DeviceRoutes.remoteCameraPage);
      }
    }
    else if(method == UTEPedConst.methodSetFutureWeather) {
      WeatherLogic weatherLogic = Get.put(WeatherLogic());
      weatherLogic.fromCacheOrRemoteLoad();
    }
    else if (method == UTEPedConst.methodLocalNotify) {
      switch(event[UTEPedConst.keyAction]) {
        case UTEPedConst.actionBluetoothStateOff:
          AppUtil.showTips(LocaleKeys.info.ts, LocaleKeys.device_not_connected.ts);
          break;

        case UTEPedConst.actionSyncDataComplete:
          HealthHelper.instance.syncHealthData();
          break;

        case UTEPedConst.actionAppNotificationCheck:
          _onAppNotificationCheck();
          break;
      }
    }
  }

  @override
  bool useMethodSupport() {
    return true;
  }

  @override
  List<String> getSupportedMethods() {
    return [UTEPedConst.methodLocalNotify];
  }

  void notifyDeviceChanged(event) {
    logger.d("notifyDeviceChanged...$event");
    for (var listener in listeners) {
      listener.onDeviceChangeEvent(event);
    }
  }

  String getStorePrefix() {
    if (_address.isNotEmpty) {
      return "${_address.replaceAll(":", "")}_";
    }

    return "";
  }

  void manualConnect() {
    if (userState == UTEPedConst.userStateConnecting) {
      logger.d("connecting");

      Timer(const Duration(seconds: 15), () {
        userState = UTEPedConst.userStateIDLE;
      });

      return;
    }
    _autoConnectDevice();
  }

  void _saveUserId(value) {
    _userId = value;
    save(UTEPedConst.keyUserId, value, global: true);

    _autoConnectDevice();
  }

  void _autoConnectDevice() {
    logger.d("_autoConnectDevice...");
    isBluetoothEnable().then((enable) => {
      if (enable) {
        isConnected().then((value) => _doConnectDevice(value))
      }
      else {
        userState == UTEPedConst.userStateIDLE,
        logger.d("bluetooth is not enabled!")
      }
    });
  }

  void _doConnectDevice(value) {
    logger.d("_doConnectDevice...");
    if (value) {
      logger.d("device already connected!");
      userState == UTEPedConst.userStateConnected;
      return;
    }
    if (_unbindAddress.isNotEmpty) {
      logger.d("device is unbinding now!");
      userState == UTEPedConst.userStateUnbinding;
      return;
    }

    if (userState == UTEPedConst.userStateConnecting
        || userState == UTEPedConst.userStateDisconnecting
        || userState == UTEPedConst.userStateUnbinding) {
      logger.d("device is busy at state $userState!");
      return;
    }

    if (_address.isNotEmpty) {
      connect(_address);

      // getBondedDevices().then((value) => logger.d("getBondedDevices: $value"));
      // getDefaultConfiguration().then((value) => logger.d("getDefaultConfiguration: $value"));
    }
    else {
      userState == UTEPedConst.userStateIDLE;
      logger.d("no device connected before");
    }
  }

  void _addDevice(deviceInfo) {
    var address = deviceInfo[UTEPedConst.keyAddress];
    if (address == null) {
      return;
    }

    bool find = false;
    for (var item in deviceList) {
      if (item[UTEPedConst.keyAddress] == address) {
        find = true;
        // update battery info
        item[UTEPedConst.keyBattery] = deviceInfo[UTEPedConst.keyBattery];
        break;
      }
    }

    if (!find) {
      deviceList.add(deviceInfo);
      save(UTEPedConst.keyDeviceList, deviceList, global: true);
    }
  }

  void _removeDevice(String address) {
    for (var item in deviceList) {
      if (item[UTEPedConst.keyAddress] == address) {
        deviceList.remove(item);
        save(UTEPedConst.keyDeviceList, deviceList, global: true);
        StorageHelper.erase(address.replaceAll(":", ""));
        break;
      }
    }
  }

  void _disconnectDevice(String address) {
    _removeDevice(address);
    if ((_address.isNotEmpty && _unbindAddress == _address) || deviceList.isEmpty) {
      _address = "";
      save(UTEPedConst.keyAddress, "", global: true);
    }

    _unbindAddress = "";
  }

  void _updateDeviceListBattery(address, battery) {
    for (var device in deviceList) {
      if (address == device[UTEPedConst.keyAddress]) {
        device[UTEPedConst.keyBattery] = battery;
        break;
      }
    }
  }

  void _startConnectTimer() {
    _cancelConnectTimer();
    _connectTimer = Timer(const Duration(seconds: 15), () {
      if (userState == UTEPedConst.userStateConnecting) {
        userState = UTEPedConst.userStateIDLE;
        if (retryCount > 0) {
          connect(_address, retryCount: retryCount - 1);
        }
        else {
          notifyDeviceChanged({UTEPedConst.keyAction: UTEPedConst.actionConnectTimeout, UTEPedConst.keyAddress:_address});
        }
      }
    });
  }

  void _cancelConnectTimer() {
    logger.d("_cancelConnectTimer...");
    _connectTimer?.cancel();
    _connectTimer = null;
  }

  Future<void> updateDeviceInfo() async {
    Map<dynamic, dynamic> deviceInfoMap = await getDeviceInfo();
    if (deviceInfoMap[UTEPedConst.keyAddress] != null) {
      deviceInfo.from(deviceInfoMap);
      battery = deviceInfoMap[UTEPedConst.keyBattery];
    }
  }

  Future<void> refreshDevice() async {
    Map<dynamic, dynamic> deviceInfoMap = await getDeviceInfo();
    if (deviceInfoMap[UTEPedConst.keyAddress] == null) {
      logger.d("refreshDevice mac address is null");
      userState = UTEPedConst.userStateIDLE;
      return;
    }
    userState = UTEPedConst.userStateConnected;
    _address = deviceInfoMap[UTEPedConst.keyAddress];
    battery = deviceInfoMap[UTEPedConst.keyBattery];
    deviceInfo.from(deviceInfoMap);

    save(UTEPedConst.keyAddress, _address, global: true);

    _addDevice(deviceInfoMap);
    _updateDeviceListBattery(deviceInfoMap[UTEPedConst.keyAddress], battery);

    notifyDeviceChanged(
        {UTEPedConst.keyAction: UTEPedConst.actionConnect, UTEPedConst.keyAddress: _address}
    );

    checkOtaStage();
  }

  Future<String> getDeviceId() async {
    if (_userId.isNotEmpty) {
      return _userId;
    }
    String? deviceId = await PlatformDeviceId.getDeviceId;
    deviceId ??= const Uuid().toString();

    return deviceId;
  }

  Future<bool> isBluetoothEnable() async {
    return bRemoteReqAsync(UTEPedConst.methodIsBluetoothEnable, {});
  }

  Future<bool> isConnected() async {
    return bRemoteReqAsync(UTEPedConst.methodIsConnected, {});
  }

  Future<void> cancelConnect() async {
    _connectTimer?.cancel();
  }

  Future<void> connect(String address, {retryCount = 0, bool fromQrCode = false}) async {
    logger.d("connect device: $address retry count: $retryCount");
    this.retryCount = retryCount;
    _address = address;
    userState = UTEPedConst.userStateConnecting;
    _startConnectTimer();
    var reqMap = {
      UTEPedConst.keyAddress: address,
      UTEPedConst.keyUserId: _userId,
      UTEPedConst.keyBindBT3: true,
      UTEPedConst.keyConnectHeadset: true,
      UTEPedConst.keyConnectA2dp: true,
      UTEPedConst.keyFromQrcode: fromQrCode
    };
    bRemoteReqAsync(UTEPedConst.methodConnect, reqMap)
        .then((value) => {
          logger.d("connect device result:$value"),
          if (value) {
            /// 这里仅用于调试使用
            ///目前手上的手表类型从sdk从状态connecting到connected状态大概需要五秒钟的时间
            /// 这个期间去请求设备信息返回的信息是不正常的，mac地址这些都为空
            // Future.delayed(const Duration(microseconds: 100), ()=> refreshDevice())
          }
          else {
            userState = UTEPedConst.userStateIDLE
          }
    });
  }

  Future<bool> disconnect(String address) async {
    logger.d("disconnect device: $address");
    userState = UTEPedConst.userStateDisconnecting;
    return bRemoteReqAsync(UTEPedConst.methodDisconnect, {UTEPedConst.keyAddress: address, UTEPedConst.keyReset: !kDebugMode});
  }

  Future<void> switchDevice(String address) async {
    logger.d("switch device: $address");
    if (_address == address) {
      return;
    }

    disconnect(_address).then((value) => {
      logger.d("disconnect result: $value"),
      if (value) {
        userState = UTEPedConst.userStateDisconnected,
        connect(address)
      }
      else {
        logger.d("disconnect failed"),
        userState = UTEPedConst.userStateIDLE
      }
    });
  }

  void _startUnbindTimer() {
    _unbindTimer?.cancel();
    _unbindTimer = Timer(const Duration(seconds: 5), () {
      logger.d("unbind timeout...");
      if (userState == UTEPedConst.userStateDisconnecting) {
        userState = UTEPedConst.userStateDisconnected;
        _disconnectDevice(_unbindAddress);
      }
      else {
        logger.d("already disconnected");
      }
    });
  }

  Future<void> unbindDevice(String address) async {
    logger.d("unbind device: $address");
    deviceInfo.reset();
    var isConnected = await this.isConnected();
    if (_address.isEmpty) {
      _address = address;
    }

    if (!isConnected) {
      _disconnectDevice(_address);
      removeOnlineDevice(_address);

      //手动解绑，设备不在线，仍然需要停止自动连接
      bRemoteReqAsync(UTEPedConst.methodDisconnect, {UTEPedConst.keyAddress: address});
    }
    else {
      _unbindAddress = _address;
      userState = UTEPedConst.userStateUnbinding;
      disconnect(address).then((value) => {
        if (value) {
          _startUnbindTimer(),
          userState = UTEPedConst.userStateDisconnecting,
        }
        else {
          userState = UTEPedConst.userStateIDLE,
        }
      });
    }

    notifyDeviceChanged({UTEPedConst.keyAction: UTEPedConst.actionDisconnect, UTEPedConst.keyAddress:address});

  }

  Future<dynamic> getDeviceInfo() async {
    var result = dRemoteReqAsync(UTEPedConst.methodGetDeviceInfo, {});
    logger.d(result);
    return result;
  }

  Future<dynamic> getBatteryInfo() async {
    var result = dRemoteReqAsync(UTEPedConst.methodGetBatteryInfo, {});
    logger.d(result);
    return result;
  }

  Future<dynamic> getBondedDevices() async {
    var result = dRemoteReqAsync(UTEPedConst.methodGetBondedDevices,  {"type": "list"});
    return result;
  }

  Future<dynamic> getDefaultConfiguration() async {
    var result = dRemoteReqAsync(UTEPedConst.methodGetDefaultConfiguration, {});
    return result;
  }

  Future<bool> isDeviceBusy() async {
    var result = bRemoteReqAsync(UTEPedConst.methodIsDeviceBusy, {});
    return result;
  }

  String getSn(address) {
    return address.replaceAll(":", "");
  }

  Future<void> checkOnlineDevices() async {
    logger.d("checkOnlineDevices");
    if (!NetTool.hasToken()) {
      logger.d("user token is empty");
      return;
    }
    _onlineBind.queryDevices(_userId).then((devices) => {
      if (devices.isNotEmpty) {
        if (_address.isEmpty) {
          // connect(devices[0]["sn"])
        }
        else {
          _syncOnlineDevices(devices)
        }
      }
      else {
        _onlineBind.startBind(_userId, getSn(_address), (value)=>{
          logger.d("bind result $value")
        })
      }
    });
  }

  Future<void> checkOtaStage() async {
    logger.d("check ota stage...");
    if (!Platform.isAndroid) {
      logger.d("only android check ota stage");
      return;
    }
    bool jlOtaStage = await _config.getNativeValue("jl_ota_stage", "bool");
    if (jlOtaStage) {
      await UpgradeHelper.instance.checkUpdate();
      if (read(UTEPedConst.keyUpgradable, defaultValue: false)) {
        logger.d("to upgrade page...");
        Get.toNamed("/device/settings/upgrade");
      }
      else {
        logger.d("newest version");
      }
    }
  }

  void _syncOnlineDevices(List devices) {
    String sn = getSn(_address);
    onlineDevices.clear();
    onlineDevices.addAll(devices);
    bool find = false;
    for (var deviceItem in devices) {
      if (deviceItem["sn"] == sn) {
        find = true;
        break;
      }
    }

    if (!find) {
      _onlineBind.startBind(_userId, sn, (value)=>{
        logger.d("bind result $value")
      });
    }
  }

  Future<void> removeOnlineDevice(String address) async {
    if (!NetTool.hasToken()) {
      logger.d("user token is empty");
      return;
    }
    _onlineBind.deleteDevice(_userId, getSn(address));
  }

  @override
  String getStoragePath() {
    return UTEPedConst.pathDevice;
  }

  void _onAppNotificationCheck() {
    Get.defaultDialog(
        title: LocaleKeys.info.ts,
        textConfirm: LocaleKeys.ok.ts,
        textCancel: LocaleKeys.cancel.ts,
        buttonColor: ColorConst.accentColor,
        middleText: LocaleKeys.notification_permission_tips.ts,
        barrierDismissible: false,
        onCancel: () => {
        },
        onConfirm: () => {
          _config.checkNotificationEnable(),
          Get.back(),
        });
  }
}